using UnityEngine;

[System.Serializable]
public enum Axis
{
	X,
	Y,
	Z
}

[System.Serializable]
public enum AxisPlane
{
	ZY,		// x-axis
	XZ,		// y-axis
	XY,		// z-axis
}

public static class MathUtils
{
	public const float TWO_PI = Mathf.PI * 2f;
	public const float HALF_PI = Mathf.PI * 0.5f;
	public const float EPSILON = 0.0000001f;

	public static Vector2 SnapPointToGrid( Vector2 v, float gridSize )
	{
		float invGridSize = 1f / gridSize;
		v.x = Mathf.Round( v.x * invGridSize ) * gridSize;
		v.y = Mathf.Round( v.y * invGridSize ) * gridSize;
		return v;
	}

	public static float SingnedAngle( Vector2 from, Vector2 to )
	{
		float cos = from.x * to.x + from.y * to.y;
		float sin = from.x * to.y - from.y * to.x;

		return Mathf.Atan2( sin, cos );
	}

	public static int Pow( int x, uint pow )
	{
		int ret = 1;
		while ( pow != 0 )
		{
			if ( ( pow & 1 ) == 1 )
				ret *= x;
			x *= x;
			pow >>= 1;
		}
		return ret;
	}

	public static void Swap<T>( ref T lhs, ref T rhs )
	{
		T temp = lhs;
		lhs = rhs;
		rhs = temp;
	}

	public static int WrapIndex( int index, int count )
	{
		return index < 0 ? index + count : index >= count ? index - count : index;
	}
		
	public static float Wrap( float x, float min, float max )
	{
		if ( min > max )
		{
			Swap( ref min, ref max );
		}
	
		if ( x < min || x > max )
		{
			float delta = max - min;
	
			if ( delta > Mathf.Epsilon )
			{
				float times = ( x - min ) / delta;
				return x - delta * Mathf.Floor( times );
			}
			
			return min;
		}
		
		return x;
	}

	public static Vector3 Wrap( Vector3 v, float min, float max )
	{
		return new Vector3(
			Wrap( v.x, min, max ),
			Wrap( v.y, min, max ),
			Wrap( v.z, min, max ) );
	}
	
	public static bool IsInPhase( float x, float phaseIn, float phaseOut, float period )
	{
		x = Wrap( x, 0.0f, period );
		phaseIn = Wrap( phaseIn, 0.0f, period );
		phaseOut = Wrap( phaseOut, 0.0f, period );
	
		if ( phaseIn > phaseOut )
			return x >= phaseIn || x <= phaseOut;
	
		return x >= phaseIn && x <= phaseOut;
	}
	
	public static float Normalize( ref Vector2 v )
	{
		float magnitude = v.magnitude;
		if ( magnitude > 1E-05f )
		{
			v *= ( 1f / magnitude );
		}
		else
		{
			v = Vector2.zero;
		}
		return magnitude;
	}

	public static float Normalize( ref Vector3 v )
	{
		float magnitude = v.magnitude;
		if ( magnitude > 1E-05f )
		{
			v *= ( 1f / magnitude );
		}
		else
		{
			v = Vector3.zero;
		}
		return magnitude;
	}

	public static Vector2 Perp( Vector2 v )
	{
		return new Vector2( -v.y, v.x );
	}

	public static float CrossProduct( Vector2 v1, Vector2 v2 )
	{
		return v1.x * v2.y - v1.y * v2.x;
	}

	public static Color RandomColor( float lower = 0f, float upper = 1f, bool alphaIsOne = true )
	{
		return new Color(
			Random.Range( lower, upper ),
			Random.Range( lower, upper ),
			Random.Range( lower, upper ),
			alphaIsOne ? 1f : Random.Range( lower, upper ) );
	}

	//-----------------------------------------------------------------------------------------------------
	// COLOR PARSING
	//-----------------------------------------------------------------------------------------------------

	/// <summary>
	/// Конвертирует струкруру UnityEngine.Color в строку в формате #RRGGBBAA, 
	/// где RR, GG, BB, AA - шестнадцатеричные значения компонент цветов
	/// </summary>
	/// <param name="c"></param>
	/// <returns></returns>
	public static string ColorToString( Color c )
	{
		byte r = (byte) ( c.r * 255f );
		byte g = (byte) ( c.g * 255f );
		byte b = (byte) ( c.b * 255f );
		byte a = (byte) ( c.a * 255f );

		return "#" + r.ToString( "X2" ) + g.ToString( "X2" ) + b.ToString( "X2" ) + a.ToString( "X2" );
	}

	/// <summary>
	/// Конвертирует строку в формате #RRGGBBAA в структуру UnityEngine.Color
	/// </summary>
	/// <param name="s"></param>
	/// <returns></returns>
	public static Color StringToColor( string s )
	{
		if ( s.Length != 9 )
		{
			Debug.LogError( "Формат строки должен быть #RRGGBBAA" );
		}

		byte[] bytes = HexStringToByteArray( s.Substring( 1, 8 ) );
		float k = 1f / 255f;

		return new Color(
			k * (float) bytes[ 0 ],
			k * (float) bytes[ 1 ],
			k * (float) bytes[ 2 ],
			k * (float) bytes[ 3 ] );
	}

	/// <summary>
	/// Конвертирует струкруру цвета в строку в формате #RRGGBB, 
	/// где RR, GG, BB - шестнадцатеричные значения компонент цветов
	/// </summary>
	/// <param name="c"></param>
	/// <returns></returns>
	public static string RgbToString( Vector3 rgb )
	{
		byte r = ( byte ) ( rgb.x * 255f );
		byte g = ( byte ) ( rgb.y * 255f );
		byte b = ( byte ) ( rgb.z * 255f );

		return "#" + r.ToString( "X2" ) + g.ToString( "X2" ) + b.ToString( "X2" );
	}

	/// <summary>
	/// Конвертирует строку в формате #RRGGBB в структуру UnityEngine.Color
	/// </summary>
	/// <param name="s"></param>
	/// <returns></returns>
	public static Vector3 StringToRgb( string s )
	{
		if ( s.Length != 7 )
			Debug.LogError( "Формат строки должен быть #RRGGBB" );

		byte[] bytes = HexStringToByteArray( s.Substring( 1, 6 ) );
		float k = 1f / 255f;

		return new Vector3(
			k * ( float ) bytes[ 0 ],
			k * ( float ) bytes[ 1 ],
			k * ( float ) bytes[ 2 ] );
	}

	/// <summary>
	/// Преобразует шестнадцатеричную строку в массив байтов
	/// </summary>
	/// <param name="hex"></param>
	/// <returns></returns>
	public static byte[] HexStringToByteArray( string hex )
	{
		if ( hex.Length % 2 == 1 )
			Debug.LogError( "The binary key cannot have an odd number of digits" );

		byte[] arr = new byte[ hex.Length >> 1 ];

		for ( int i = 0; i < hex.Length >> 1; ++i )
		{
			arr[ i ] = (byte) ( ( GetHexVal( hex[ i << 1 ] ) << 4 ) + ( GetHexVal( hex[ ( i << 1 ) + 1 ] ) ) );
		}

		return arr;
	}

	/// <summary>
	/// Преобразует символ шестнадцатиричной цифры в десятичное целое число
	/// </summary>
	/// <param name="hex"></param>
	/// <returns></returns>
	public static int GetHexVal( char hex )
	{
		int val = (int) hex;
		//For uppercase A-F letters:
		//return val - ( val < 58 ? 48 : 55 );
		//For lowercase angle-f letters:
		//return val - (val < 58 ? 48 : 87);
		//Or the two combined, but angle bit slower:
		return val - (val < 58 ? 48 : (val < 97 ? 55 : 87));
	}

	public static bool HasAlpha( TextureFormat format )
	{
		switch ( format )
		{
			case TextureFormat.Alpha8:
			case TextureFormat.ARGB4444:
			case TextureFormat.RGBA32:
			case TextureFormat.ARGB32:
			case TextureFormat.DXT5:
			case TextureFormat.RGBA4444:
			case TextureFormat.BGRA32:
			case TextureFormat.PVRTC_RGBA2:
			case TextureFormat.PVRTC_RGBA4:
			case TextureFormat.ATC_RGBA8:
			case TextureFormat.ATF_RGBA_JPG:
			case TextureFormat.ETC2_RGBA1:
			case TextureFormat.ETC2_RGBA8:
			case TextureFormat.ASTC_RGBA_12x12:
			case TextureFormat.ASTC_RGBA_10x10:
			case TextureFormat.ASTC_RGBA_8x8:
			case TextureFormat.ASTC_RGBA_6x6:
			case TextureFormat.ASTC_RGBA_5x5:
			case TextureFormat.ASTC_RGBA_4x4:
				return true;
		}
		return false;
	}

	//-----------------------------------------------------------------------------------------------------
	// Closest Points / Distances
	//-----------------------------------------------------------------------------------------------------

	/// <summary>
	/// Возвращает кратчайший путь (со знаком) от значения from до значения to, однако перед этим 
	/// углы "заворчиваются" в границы [ min, max ].
	/// Т.е. сколько нужно прибавить к заврапленному from, чтобы получить заврапленный to.
	/// Пример 1:
	/// ClosestWrappedDistance( 90f, 300f, 0f, 360f ) возвратит -150f, потому что если 
	/// к from прибавить -150f и заврапить в границы ( -60f => [ 0f, 360f ] ) будет 300f, т.е. наш to,
	/// однако знак отрицательный, потому что в противоположную сторону (210f) путь длиннее
	/// Пример 2:
	/// ClosestWrappedDistance( -340f, 380f, 0f, 360f ) и
	/// ClosestWrappedDistance( 90f, -270f, 0f, 360f )
	/// возвратят нули потому пары from и to представляют 
	/// одно и то же значение, следовательно мы уже находимся там где нужно
	/// </summary>
	/// <param name="_from"></param>
	/// <param name="to"></param>
	/// <param name="min"></param>
	/// <param name="max"></param>
	/// <returns></returns>
	public static float ClosestWrappedDistance( float _from, float to, float min, float max )
	{
		if ( min > max )
		{
			Swap( ref min, ref max );
		}

		_from = Wrap( _from, min, max );
		to = Wrap( to, min, max );

		float d1;	// positive direction
		float d2;	// negative direction

		if ( _from < to )
		{
			d1 = to - _from;
			d2 = ( _from - min ) + ( max - to );
		}
		else
		{
			d1 = ( max - _from ) + ( to - min );
			d2 = _from - to;
		}

		return ( d1 < d2 ) ? d1 : -d2;
	}

	/// <summary>
	/// Линейная интерполяция по кратчайшему двух значений, которые "наматываются" на интервал [ min, max ]
	/// </summary>
	/// <param name="from"></param>
	/// <param name="to"></param>
	/// <param name="min"></param>
	/// <param name="max"></param>
	/// <param name="t"></param>
	/// <returns></returns>
	public static float ClosestWrappedLerp( float from, float to, float min, float max, float t )
	{
		float delta = ClosestWrappedDistance( from, to, min, max );
		return Wrap( from + delta * t, min, max );
	}

	/// <summary>
	/// Находим центр окружности основываясь на трех точках.
	/// (Центром описанной окружности трех точек является пересечение серединных перпендикуляров отрезков AB и BC.)
	/// </summary>
	/// <param name="a"></param>
	/// <param name="b"></param>
	/// <param name="c"></param>
	/// <param name="result">Ссылка на возвращаемое значение</param>
	/// <returns>Возвращает true, если точки не лежат на одной прямой</returns>
	public static void CircleCenterByThreePoints( Vector3 a, Vector3 b, Vector3 c, ref Vector3 result )
	{
		Vector3 ab = b - a;
		Vector3 bc = c - b;

		Vector3 abPoint = a + ab * 0.5f;
		Vector3 bcPoint = b + bc * 0.5f;

		Vector3 perp = Vector3.Cross( ab, bc );
		
		Vector3 abDir = Vector3.Cross( perp, ab );
		Vector3 bcDir = Vector3.Cross( perp, bc );

		Vector3 unused = new Vector3();
		ClosestPointsBetweenTwoLines( abPoint, abDir, bcPoint, bcDir, ref result, ref unused );
	}

	/// <summary>
	/// Находит кратчайший отрезок *Result между двумя линиями с точками *Point и направлениями *Dir
	/// </summary>
	/// <param name="aPoint"></param>
	/// <param name="aDir"></param>
	/// <param name="bPoint"></param>
	/// <param name="bDir"></param>
	/// <param name="aResult"></param>
	/// <param name="bResult"></param>
	public static void ClosestPointsBetweenTwoLines( Vector3 aPoint, Vector3 aDir, Vector3 bPoint, Vector3 bDir, ref Vector3 aResult, ref Vector3 bResult )
	{
		Vector3 w0 = aPoint - bPoint;
		float a = Vector3.Dot( aDir, aDir );
		float b = Vector3.Dot( aDir, bDir );
		float c = Vector3.Dot( bDir, bDir );
		float d = Vector3.Dot( aDir, w0 );
		float e = Vector3.Dot( bDir, w0 );
		
		float denom = a * c - b * b;
		if ( Mathf.Abs( denom ) < Mathf.Epsilon )
		{
			aResult = aPoint;
			bResult = bPoint + ( e / c ) * bDir;
		}
		else
		{
			aResult = aPoint + ( ( b * e - c * d ) / denom ) * aDir;
			bResult = bPoint + ( ( a * e - b * d ) / denom ) * bDir;
		}
	}

	/// <summary>
	/// Ближайшая точка на отрезке
	/// </summary>
	/// <param name="P">Наша точка</param>
	/// <param name="A">Начало отрезка</param>
	/// <param name="B">Конец отрезка</param>
	/// <returns></returns>
	public static Vector2 ClosestPointOnSegment( Vector2 P, Vector2 A, Vector2 B )
	{
		Vector2 AP = P - A;
		Vector2 AB = B - A;

		float t = Vector2.Dot( AP, AB ) / Vector2.Dot( AB, AB );
		t = Mathf.Clamp01( t );

		return A + AB * t;
	}

	/// <summary>
	/// Ближайшая точка на отрезке с исключенными концами отрезка
	/// </summary>
	/// <param name="P">Наша точка</param>
	/// <param name="A">Начало отрезка</param>
	/// <param name="B">Конец отрезка</param>
	/// <param name="excludeCoeff">Коеффициент длины отрезка, который будет "урезан" с обоих концов"</param>
	/// <returns></returns>
	public static Vector2 ClosestPointOnSegment( Vector2 P, Vector2 A, Vector2 B, float excludeCoeff )
	{
		Vector2 AP = P - A;
		Vector2 AB = B - A;

		float t = Vector2.Dot( AP, AB ) / Vector2.Dot( AB, AB );
		t = Mathf.Clamp( t, excludeCoeff, 1f - excludeCoeff );

		return A + AB * t;
	}

	//-----------------------------------------------------------------------------------------------------
	// Rect Utils
	//-----------------------------------------------------------------------------------------------------

	public static Rect AddPointToRect( Rect r, Vector2 point )
	{
		r.xMin = Mathf.Min( r.xMin, point.x );
		r.yMin = Mathf.Min( r.yMin, point.y );
		r.xMax = Mathf.Max( r.xMax, point.x );
		r.yMax = Mathf.Max( r.yMax, point.y );
		return r;
	}

	public static Rect GetTaperRect( Rect r, float offset )
	{
		r.xMin += offset;
		r.yMin += offset;
		r.xMax -= offset;
		r.yMax -= offset;
		return r;
	}

	public static float GetRectMin( Rect r, int axis )
	{
		return axis == 0 ? r.xMin : r.yMin;
	}

	public static float GetRectMax( Rect r, int axis )
	{
		return axis == 0 ? r.xMax : r.yMax;
	}
	
	//-----------------------------------------------------------------------------------------------------
	// Handedness
	//-----------------------------------------------------------------------------------------------------

	public static Vector3 LeftHand( Vector3 v )
	{
		return new Vector3( v.x, v.z, v.y );
	}

	public static Vector3[] LeftHand( Vector3[] rotMatrix )
	{
		return LeftHand( rotMatrix[ 0 ], rotMatrix[ 1 ], rotMatrix[ 2 ] );
	}

	public static Vector3[] LeftHand( Vector3 rotX, Vector3 rotY, Vector3 rotZ )
	{
		Vector3 rot_x = rotX;
		Vector3 rot_y = rotY;
		Vector3 rot_z = rotZ;

		rot_x.x = rotX.x;
		rot_x.y = rotX.z;
		rot_x.z = rotX.y;

		rot_y.x = rotZ.x;
		rot_y.y = rotZ.z;
		rot_y.z = rotZ.y;

		rot_z.x = rotY.x;
		rot_z.y = rotY.z;
		rot_z.z = rotY.y;

		return new Vector3[] { rot_x, rot_y, rot_z };
	}

	public static Quaternion LeftHandQuat( Vector3 rotX, Vector3 rotY, Vector3 rotZ )
	{
		Vector3[] lhanded = LeftHand( rotX, rotY, rotZ );
		return Quaternion.LookRotation( lhanded[ 2 ], lhanded[ 1 ] );
	}

	public static Quaternion LeftHand( Quaternion q )
	{
		//Matrix4x4 m = Matrix4x4.TRS( Vector3.zero, q, Vector3.one );
		Matrix4x4 m = QuatToMatrix( q );

		// Transpose (RH -> LH)
		Vector3 rotX = new Vector3( m[ 0, 0 ], m[ 0, 1 ], m[ 0, 2 ] );	// column-vector Rx
		Vector3 rotY = new Vector3( m[ 1, 0 ], m[ 1, 1 ], m[ 1, 2 ] );	// column-vector Ry
		Vector3 rotZ = new Vector3( m[ 2, 0 ], m[ 2, 1 ], m[ 2, 2 ] );	// column-vector Rz

		return LeftHandQuat( rotX, rotY, rotZ );
	}

	//-----------------------------------------------------------------------------------------------------
	// Matrix & Quaternion utils
	//-----------------------------------------------------------------------------------------------------

	/// <summary>
	/// src: http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/
	/// </summary>
	/// <param name="q"></param>
	public static Matrix4x4 QuatToMatrix( Quaternion q )
	{
		Matrix4x4 m = Matrix4x4.identity;

		float sqw = q.w * q.w;
		float sqx = q.x * q.x;
		float sqy = q.y * q.y;
		float sqz = q.z * q.z;

		// invs (inverse square length) is only required if quaternion is not already normalised
		float invs = 1f / ( sqx + sqy + sqz + sqw );
		m[ 0, 0 ] = ( sqx - sqy - sqz + sqw ) * invs; // since sqw + sqx + sqy + sqz =1/invs*invs
		m[ 1, 1 ] = ( -sqx + sqy - sqz + sqw ) * invs;
		m[ 2, 2 ] = ( -sqx - sqy + sqz + sqw ) * invs;

		float tmp1 = q.x * q.y;
		float tmp2 = q.z * q.w;
		m[ 1, 0 ] = 2f * ( tmp1 + tmp2 ) * invs;
		m[ 0, 1 ] = 2f * ( tmp1 - tmp2 ) * invs;

		tmp1 = q.x * q.z;
		tmp2 = q.y * q.w;
		m[ 2, 0 ] = 2f * ( tmp1 - tmp2 ) * invs;
		m[ 0, 2 ] = 2f * ( tmp1 + tmp2 ) * invs;
		tmp1 = q.y * q.z;
		tmp2 = q.x * q.w;
		m[ 2, 1 ] = 2f * ( tmp1 + tmp2 ) * invs;
		m[ 1, 2 ] = 2f * ( tmp1 - tmp2 ) * invs;

		return m;
	}

	public static Vector3[] QuatToThreeVectors( Quaternion q )
	{
		Vector3[] v = new Vector3[3];

		float sqw = q.w * q.w;
		float sqx = q.x * q.x;
		float sqy = q.y * q.y;
		float sqz = q.z * q.z;

		// invs (inverse square length) is only required if quaternion is not already normalised
		float invs = 1f / ( sqx + sqy + sqz + sqw );
		v[ 0 ].x = ( sqx - sqy - sqz + sqw ) * invs; // since sqw + sqx + sqy + sqz =1/invs*invs
		v[ 1 ].y = ( -sqx + sqy - sqz + sqw ) * invs;
		v[ 2 ].z = ( -sqx - sqy + sqz + sqw ) * invs;

		float tmp1 = q.x * q.y;
		float tmp2 = q.z * q.w;
		v[ 0 ].y = 2f * ( tmp1 + tmp2 ) * invs;
		v[ 1 ].x = 2f * ( tmp1 - tmp2 ) * invs;

		tmp1 = q.x * q.z;
		tmp2 = q.y * q.w;
		v[0 ].z = 2f * ( tmp1 - tmp2 ) * invs;
		v[ 2 ].x = 2f * ( tmp1 + tmp2 ) * invs;

		tmp1 = q.y * q.z;
		tmp2 = q.x * q.w;
		v[ 1 ].z = 2f * ( tmp1 + tmp2 ) * invs;
		v[ 2 ].y = 2f * ( tmp1 - tmp2 ) * invs;

		return v;
	}

	//-----------------------------------------------------------------------------------------------------
	// Axis utils
	//-----------------------------------------------------------------------------------------------------

	public static Vector3 AxisToVector3( Axis axis )
	{
		switch ( axis )
		{
			case Axis.X: return Vector3.right;
			case Axis.Y: return Vector3.up;
			case Axis.Z: return Vector3.forward;
		}
		throw new UnityException( "Undefined axis enum value: " + axis );
	}

	public static Axis AxisSecondary( Axis axis )
	{
		return axis == Axis.X ? Axis.Y : axis == Axis.Y ? Axis.Z : Axis.X;
	}

	public static Quaternion AxisRotation( Axis localXAxis )
	{
		Axis localYAxis = AxisSecondary( localXAxis );
		Axis localZAxis = AxisSecondary( localYAxis );

		Vector3 Y = AxisToVector3( localYAxis );
		Vector3 Z = AxisToVector3( localZAxis );

		return Quaternion.LookRotation( Z, Y );
	}
}